﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework.Constraints;
using NUnit.Framework;

namespace PSMockTest
{
	/// <summary>
	/// Class used in unit testing to assert/verify exception throwing
	/// </summary>
	public static class Verify
	{
		public static void That(Action actual, IThrowExceptionConstraint constraint)
		{
			if (!constraint.Matches(actual))
			{
				throw new AssertionException("Exception assert.\n" + constraint.WriteDescriptionTo() + "\n"
				 + constraint.WriteActualValueTo());
			}
		}
	}

	/// <summary>
	/// Class used in unit testing to assert/verify exception throwing
	/// </summary>
	public static class Throws
	{
		public static IThrowExceptionConstraint Exception<T>() where T:Exception
		{
			return new ThrowExceptionConstraint<T>();
		}

		public static IThrowExceptionConstraint Exception<T>(Action<T> excptionCheck) where T : Exception
		{
			return new ThrowExceptionConstraint<T>(excptionCheck);
		}
	}

	public interface IThrowExceptionConstraint 
	{
		bool Matches(Action actual);
		string WriteDescriptionTo();
		string WriteActualValueTo();
	}

	public class ThrowExceptionConstraint<T> : IThrowExceptionConstraint where T:Exception
	{
		private Action typedActual;
		private string expectedString;
		private string actualString;
		private Action<T> excptionCheck = null;


		public ThrowExceptionConstraint()
		{
			expectedString = typeof(T).FullName;
		}
		public ThrowExceptionConstraint(Action<T> excptionCheck)
		{
			expectedString = typeof(T).FullName;
			this.excptionCheck = excptionCheck;
		}

		/// <summary>
		/// Test whether the constraint is matched by the actual value.
		/// This is a template method, which calls the IsMatch method
		/// of the derived class.
		/// </summary>
		public bool Matches(Action actual)
		{
			typedActual = actual;

			Type catchedType = null;
			T catchedException = default(T);
			try
			{
				typedActual();
			}
			catch (T e)
			{
				catchedType = e.GetType();
				catchedException = e;
			}
			catch (Exception e)
			{
				catchedType = e.GetType();
			}

			if (catchedType == null)
			{
				actualString = "No exception thrown";
			}
			else if (typeof(T).IsAssignableFrom(catchedType))
			{
				if (excptionCheck != null)
				{
					excptionCheck(catchedException);
				}
				return true;
			}
			else
			{
				actualString = catchedType.FullName;
			}
			return false;
		}


		/// <summary>
		/// Writes expected value as a string
		/// </summary>
		public string WriteDescriptionTo()
		{
			return "Expected: " + expectedString;
		}


		/// <summary>
		/// Writes actual value as a string
		/// </summary>
		public string WriteActualValueTo()
		{
			return "But was:  " + actualString;
		}
	}
}
